﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Globalization;
using System.Resources;
using System.Reflection;

namespace SmartFront.Toolkit.Core
{
    /// <summary>
    /// Exception related extension methods
    /// </summary>
    public static class ExceptionExtensionMethods
    {
        internal const string ExceptionFormaterLine = "===============================";

        internal const string FormatParams = "_FormatParams_";

        internal const string TargetInvocationExceptionRemoteStackTraceField = "_remoteStackTraceString";

        /// <summary>
        /// Provides a localized message for the exception in the culture passed.
        /// </summary>
        /// <param name="ex">The exception.</param>
        /// <param name="culture">The culture.</param>
        /// <param name="resourceManager">The resource manager to lookup the message resource (typename with _ instead of .'s).</param>
        /// <returns>A localized message for the exception; otherwise the default message.</returns>
        public static string ToLocalizedString(this Exception ex, CultureInfo culture, ResourceManager resourceManager)
        {
            if (null == ex)
            {
                return String.Empty;
            }

            if (null != culture && null != resourceManager)
            {
                ResourceSet resouces = resourceManager.GetResourceSet(culture, true, true);
                string resourceKey = ex.GetType().FullName.Replace('.', '_');
                string resourceValue = resouces.GetString(resourceKey);

                if (!String.IsNullOrEmpty(resourceValue))
                {
                    if (ex.Data.Contains(FormatParams))
                    {
                        IEnumerable<object> parameters = (IEnumerable<object>)ex.Data[FormatParams];
                        return String.Format(culture, resourceValue, parameters.ToArray<object>());
                    }
                    else
                    {
                        return resourceValue;
                    }
                }
            }

            return ex.Message;
        }

        /// <summary>
        /// Sets the localized format parameters used by ToLocalizedString
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <param name="parameters">The parameters.</param>
        public static void SetLocalizedFormatParameters(this Exception ex, IEnumerable<object> parameters)
        {
            if (ex.Data.Contains(FormatParams))
            {
                ex.Data.Remove(FormatParams);
            }

            ex.Data.Add(FormatParams, parameters);
        }

        /// <summary>
        /// Unwraps the specified <see cref="TargetInvocationException"/> and preserves the stack trace on rethrow.
        /// </summary>
        /// <param name="ex">The exception to be unwrapped</param>
        /// <returns>The unwrapped exception</returns>
        public static Exception Unwrap(this TargetInvocationException ex)
        {
            if (null == ex || null == ex.InnerException)
            {
                return ex;
            }

            FieldInfo remoteStack = typeof(Exception).GetField(TargetInvocationExceptionRemoteStackTraceField, BindingFlags.Instance | BindingFlags.NonPublic);
            string innerRemoteStack = (string)remoteStack.GetValue(ex.InnerException);
            string replacementStack = String.Join(Environment.NewLine + ExceptionFormaterLine + Environment.NewLine, new string[] { ex.InnerException.StackTrace, ex.StackTrace, innerRemoteStack });
            remoteStack.SetValue(ex.InnerException, replacementStack);
            return ex.InnerException;
        }
    }
}
